#include "WorldRayCast.h"

#include "App/Game.h"

WorldRayCast::WorldRayCast(const glm::vec3 &start, const glm::vec3 &end, World &world)
    : m_start(start), m_end(end), m_world(world)
{
  Do();
}

WorldRayCast::WorldRayCast(const glm::vec3 &start, const glm::vec3 &direction, float reach, World &world)
    : m_start(start), m_world(world)
{
  m_end = start + direction * reach;
  Do();
}

void WorldRayCast::Do()
{
  const auto level = Game::GetInstance().GetLevel();
  if (level == nullptr)
    return;

  glm::vec3 start = m_start;
  glm::vec3 end = m_end;

  Ref<BlockRegistry> blockReg = level->GetBlockRegistry();

  if (!glm::any(glm::isnan(start)) && !glm::any(glm::isnan(end)))
  {
    // 起点方块坐标
    glm::ivec3 startBlockPos = glm::floor(start);
    // 终点方块坐标
    glm::ivec3 endBlockPos = glm::floor(end);

    // 检测起点方块
    if (BlockData *startBlock = m_world.GetBlockAt(startBlockPos))
    {
      const Block::ID id = startBlock->id;
      Block *startBlockType = blockReg->GetBlock(id);
      assert(startBlockType != nullptr);
      if (id > 0 && startBlockType->modelType != Block::BlockModelType::Luqid)
      {
        Ref<BlockModel> model = startBlockType->model;
        if (model && model->IsRayIntersectElements(start - glm::vec3(startBlockPos), end - glm::vec3(startBlockPos)))
        {
          m_successful = true;

          m_hitTarget = {
              .blockPosition = startBlockPos,
              .block = startBlock,
              .blockType = startBlockType,
          };
          return;
        }
      }
    }

    int cnt = 200;
    glm::ivec3 previousPos = startBlockPos;
    while (cnt--)
    {
      if (glm::any(glm::isnan(start)))
        return;

      // 检测了终点方块
      if (startBlockPos == endBlockPos)
        return;

      // 当前方块和重点方块XYZ不同（向某方向选了候选方块）
      bool xChanged = true;
      bool yChanged = true;
      bool zChanged = true;
      // 个方向候选方块坐标
      float newX, newY, newZ;

      // 方式向各个方向选取方块
      if (startBlockPos.x < endBlockPos.x)
        newX = startBlockPos.x + 1;
      else if (startBlockPos.x > endBlockPos.x)
        newX = startBlockPos.x;
      else
        xChanged = false;

      if (startBlockPos.y < endBlockPos.y)
        newY = startBlockPos.y + 1;
      else if (startBlockPos.y > endBlockPos.y)
        newY = startBlockPos.y;
      else
        yChanged = false;

      if (startBlockPos.z < endBlockPos.z)
        newZ = startBlockPos.z + 1;
      else if (startBlockPos.z > endBlockPos.z)
        newZ = startBlockPos.z;
      else
        zChanged = false;

      // 计算各方向方块离当前位置有多近
      glm::vec3 scale{999.f};
      glm::vec3 d = end - start;

      // 向某方向选取了方块
      if (xChanged)
        scale.x = (newX - start.x) / d.x;
      if (yChanged)
        scale.y = (newY - start.y) / d.y;
      if (zChanged)
        scale.z = (newZ - start.z) / d.z;

      // 最终选取了哪个方向的候选方块
      uint8_t direction;
      // 选出候选方块中离当前最近的，更新起点、要检测的方块坐标
      if (scale.x < scale.y && scale.x < scale.z)
      {
        if (endBlockPos.x > startBlockPos.x)
          direction = Direction::PositiveX; // x+
        else
          direction = Direction::NegtiveX; // x-;

        start = {newX, start.y + d.y * scale.x, start.z + d.z * scale.x};
      }
      else if (scale.y < scale.z)
      {
        if (endBlockPos.y > startBlockPos.y)
          direction = Direction::PositiveY; // y+
        else
          direction = Direction::NegtiveY; // y-;

        start = {start.x + d.x * scale.y, newY, start.z + d.z * scale.y};
      }
      else
      {
        if (endBlockPos.z > startBlockPos.z)
          direction = Direction::PositiveZ; // z+
        else
          direction = Direction::NegtiveZ; // z-;

        start = {start.x + d.x * scale.z, start.y + d.y * scale.z, newZ};
      }

      startBlockPos.x = glm::floor(start.x);
      startBlockPos.y = glm::floor(start.y);
      startBlockPos.z = glm::floor(start.z);
      switch (direction)
      {
      case Direction::NegtiveX: // x-
                                // 以方块内各轴最小坐标为方块坐标，这里得到的是X上最大坐标所以要-1
        startBlockPos.x--;
        break;
      case Direction::NegtiveY: // y-
        startBlockPos.y--;
        break;
      case Direction::NegtiveZ: // z-
        startBlockPos.z--;
      }

      // 检测新起点方块
      if (BlockData *startBlock = m_world.GetBlockAt(startBlockPos))
      {
        const Block::ID id = startBlock->id;
        Block *startBlockType = blockReg->GetBlock(id);
        assert(startBlockType != nullptr);
        if (id > 0 && startBlockType->modelType != Block::BlockModelType::Luqid)
        {
          Ref<BlockModel> model = startBlockType->model;
          //if (startBlockType->name == "torch")
          if (model && model->IsRayIntersectElements(start - glm::vec3(startBlockPos), end - glm::vec3(startBlockPos)))
          {
            m_successful = true;
            m_hitTarget = {
                .blockPosition = startBlockPos,
                .neighborPosition = previousPos,
                .hasNeighbor = true,
                .block = startBlock,
                .blockType = startBlockType,
            };
            return;
          }
        }
      }
      previousPos = startBlockPos;
    }
  }
}
